ASP.NET MVC 是 Microsoft 的 Web 應用框架,隨著每一代的改版,它都帶來了新的功能和改進。以下是各版本 ASP.NET MVC 的主要變化和差異:
傳統的 URL 參數:/products?id=123
使用路由的 URL:/products/123
ASP.NET MVC 中的早期路由配置
1. Global.asax 文件
在 ASP.NET MVC 中,路由配置通常放在 Global.asax 文件的 Application_Start 方法中。這裡的路由配置會使用 RouteCollection 來定義 URL 與控制器的映射關係。以下是一個示例:
csharp
複製程式碼
using System.Web.Mvc;
using System.Web.Routing;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
在這個例子中,RegisterRoutes 方法定義了一個基本的路由規則,這些規則會將 URL 映射到對應的控制器和動作方法上。這裡的 url 模式為 "{controller}/{action}/{id}",這表示 URL 的結構是控制器名、動作方法名和可選的 id 參數。
2. 硬編碼的路由規則
早期的路由配置往往需要硬編碼路由規則。雖然這種方式可以解決基本的需求,但不夠靈活,且難以擴展。
3. 不支持高級路由功能
早期版本的 ASP.NET MVC 不支持像當前版本那樣的高級路由功能(如路由屬性、反向路由、路由約束等)。路由配置主要依賴於在 Global.asax 中設置的簡單規則。
現代 ASP.NET Core 中的路由配置
在 ASP.NET Core 中,路由配置移到了 Startup.cs 或 Program.cs 文件中。以下是一個現代 ASP.NET Core 的路由配置示例:
Program.cs 示例(ASP.NET Core 6.0 及更高版本):
csharp
複製程式碼
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
app.Run();
Startup.cs 示例(ASP.NET Core 5.0 及更早版本):
csharp
複製程式碼
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
在現代 ASP.NET Core 中,路由配置變得更加靈活和強大,支持更多高級功能和自定義選項,而不僅僅依賴於基本的 URL 結構和硬編碼規則。
在現代的 MVC 框架中,路由屬性、反向路由和路由約束是一些高級功能,用於增強路由配置的靈活性和功能性。以下是對這些功能的詳細說明及示範:
1. 路由屬性(Attribute Routing)
路由屬性允許你在控制器和動作方法上直接定義路由規則。這種方法可以讓路由規則更接近控制器和動作方法,使代碼更具可讀性和可維護性。
示範:
在 ASP.NET Core MVC 中,你可以使用 [Route] 屬性來定義路由規則:
csharp
複製程式碼
using Microsoft.AspNetCore.Mvc;
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// GET api/products
[HttpGet]
public IActionResult GetAllProducts()
{
// 處理請求
return Ok();
}
// GET api/products/5
[HttpGet("{id}")]
public IActionResult GetProductById(int id)
{
// 處理請求
return Ok();
}
// POST api/products
[HttpPost]
public IActionResult CreateProduct([FromBody] Product product)
{
// 處理請求
return CreatedAtAction(nameof(GetProductById), new { id = product.Id }, product);
}
}
在這個例子中:
[Route("api/[controller]")] 定義了控制器的基本路由。
[HttpGet("{id}")] 定義了用於獲取特定產品的路由,其中 {id} 是動態路由參數。
2. 反向路由(Reverse Routing)
反向路由是根據控制器和動作方法生成 URL。這樣可以避免硬編碼 URL,並使代碼更易於維護。反向路由通常用於生成鏈接和進行重定向。
示範:
在 ASP.NET Core MVC 中,你可以使用 Url.Action 方法來生成 URL:
csharp
複製程式碼
public IActionResult Index()
{
// 生成一個指向 ProductsController 的 GetProductById 動作的 URL
string url = Url.Action("GetProductById", "Products", new { id = 5 });
// URL 可能是 "/api/products/5"
return Content($"Generated URL: {url}");
}
你也可以使用 RedirectToAction 進行重定向:
csharp
複製程式碼
public IActionResult RedirectToProduct()
{
// 重定向到 ProductsController 的 GetProductById 動作
return RedirectToAction("GetProductById", "Products", new { id = 5 });
}
3. 路由約束(Route Constraints)
路由約束允許你定義 URL 參數的特定規則,例如數字、字母或特定格式。這可以幫助確保路由參數符合預期格式,並限制路由匹配的範圍。
示範:
在 ASP.NET Core MVC 中,你可以使用約束來限制路由參數的格式:
csharp
複製程式碼
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}",
constraints: new { id = @"\d+" } // id 參數必須是數字
);
});
如果你在控制器中需要更詳細的約束,可以這樣做:
csharp
複製程式碼
[Route("products/{id:int}")]
public IActionResult GetProductById(int id)
{
// 處理請求
return Ok();
}
在這個例子中:
{id:int} 限制了 id 參數必須是整數。這樣的約束可以防止無效的 id 參數(如字母或特殊字符)被接受。
這些高級路由功能可以幫助你更靈活地定義和管理 URL 路由,使得應用程序的路由更加健壯和易於維護。
單元測試支持:MVC 的架構設計更利於單元測試。
MVC 架構本身的設計非常適合單元測試,因為它將應用邏輯分為獨立的層次:模型(Model)、視圖(View)、控制器(Controller)。其中,控制器是單元測試的重點,因為它負責處理業務邏輯、處理請求、協調模型和視圖之間的交互。
進行單元測試的主要目標是對控制器中的業務邏輯進行測試,而不是與數據庫或視圖進行交互。為了做到這一點,我們通常會使用**模擬(Mock)**來替代實際的外部依賴。
以下是如何進行 MVC 單元測試的步驟和示範:
1. 環境設置
在 ASP.NET Core MVC 中,我們通常使用 xUnit 或 NUnit 作為單元測試框架,並且使用 Moq 庫來進行依賴注入的模擬。
安裝相關的 NuGet 包:
xUnit 或 NUnit:單元測試框架
Moq:模擬對象的庫,用於替換外部依賴
bash
複製程式碼
dotnet add package xunit
dotnet add package Moq
dotnet add package Microsoft.AspNetCore.Mvc.Testing
2. 控制器示例
假設我們有一個 ProductsController,它使用一個服務 IProductService 來處理業務邏輯。下面是控制器的簡單示例:
csharp
複製程式碼
public class ProductsController : Controller
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
public IActionResult GetProductById(int id)
{
var product = _productService.GetProductById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
ProductsController 通過 IProductService 服務來獲取產品數據,這樣的設計有助於單元測試,因為我們可以模擬這個服務。
3. 單元測試示範
以下是使用 xUnit 和 Moq 進行單元測試的基本步驟:
3.1 模擬依賴(Mocking Dependencies)
我們將使用 Moq 庫來模擬 IProductService,並測試 ProductsController 的行為。
3.2 單元測試代碼
csharp
複製程式碼
using Moq;
using Xunit;
using Microsoft.AspNetCore.Mvc;
using MyApp.Controllers;
using MyApp.Services;
using MyApp.Models;
public class ProductsControllerTests
{
[Fact]
public void GetProductById_ReturnsOkResult_WhenProductExists()
{
// Arrange
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(service => service.GetProductById(1))
.Returns(new Product { Id = 1, Name = "Test Product" });
var controller = new ProductsController(mockProductService.Object);
// Act
var result = controller.GetProductById(1);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var product = Assert.IsType<Product>(okResult.Value);
Assert.Equal(1, product.Id);
}
[Fact]
public void GetProductById_ReturnsNotFoundResult_WhenProductDoesNotExist()
{
// Arrange
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(service => service.GetProductById(1))
.Returns((Product)null);
var controller = new ProductsController(mockProductService.Object);
// Act
var result = controller.GetProductById(1);
// Assert
Assert.IsType<NotFoundResult>(result);
}
}
4. 測試解釋
4.1 測試 GetProductById 成功情況
csharp
複製程式碼
[Fact]
public void GetProductById_ReturnsOkResult_WhenProductExists()
{
// Arrange
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(service => service.GetProductById(1))
.Returns(new Product { Id = 1, Name = "Test Product" });
var controller = new ProductsController(mockProductService.Object);
// Act
var result = controller.GetProductById(1);
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
var product = Assert.IsType<Product>(okResult.Value);
Assert.Equal(1, product.Id);
}
解釋:
Arrange:使用 Moq 庫創建一個模擬的 IProductService,並設置它的 GetProductById 方法返回一個產品對象。
Act:調用 ProductsController 的 GetProductById 方法。
Assert:檢查返回的結果是否是 OkObjectResult,並檢查其中的產品對象是否正確。
4.2 測試 GetProductById 產品不存在的情況
csharp
複製程式碼
[Fact]
public void GetProductById_ReturnsNotFoundResult_WhenProductDoesNotExist()
{
// Arrange
var mockProductService = new Mock<IProductService>();
mockProductService.Setup(service => service.GetProductById(1))
.Returns((Product)null);
var controller = new ProductsController(mockProductService.Object);
// Act
var result = controller.GetProductById(1);
// Assert
Assert.IsType<NotFoundResult>(result);
}
解釋:
Arrange:模擬的 IProductService 返回 null,表示沒有找到產品。
Act:調用 ProductsController 的 GetProductById 方法。
Assert:檢查返回的結果是否是 NotFoundResult。
5. 執行測試
運行單元測試可以使用 Visual Studio 的內置測試工具,或者使用命令行工具進行:
bash
複製程式碼
dotnet test
6. 小結
模擬依賴:使用 Moq 等庫模擬外部依賴,這樣可以專注於測試控制器中的業務邏輯。
Arrange-Act-Assert:常用的單元測試結構,分為準備數據、執行測試和驗證結果三個步驟。
單一責任原則:控制器應該只處理業務邏輯,而依賴的外部服務應該通過依賴注入進行管理,這有助於單元測試的進行。
這樣的設計和測試方法有助於保持代碼的高質量和易於維護。
好的,讓我詳細解釋一下 表單字段中的數據 與 Product 對象的屬性 如何進行繫結。這個過程稱為 模型繫結,是 ASP.NET Core 框架的一個重要特性。
基本概念
表單字段中的數據 是指用戶在 HTML 表單中填寫的數據。例如,表單可能包含 <input> 或其他表單控件,這些控件的 name 屬性對應於模型的屬性名稱。
Product 對象的屬性 是控制器中用來接收數據的 C# 類中的屬性,例如 Name、Price 等。
當用戶提交表單時,ASP.NET Core 會自動將這些表單中的字段值繫結到控制器中 Product 對象的對應屬性上。
具體示範
假設我們有一個簡單的 Product 模型,如下:
1. Product 模型 (C# 類):
csharp
複製程式碼
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
這個 Product 類有三個屬性:Id、Name 和 Price。
2. HTML 表單 (前端):
html
複製程式碼
<form method="post" action="/products/create">
<label for="name">Name:</label>
<input type="text" id="name" name="Name" />
<label for="price">Price:</label>
<input type="text" id="price" name="Price" />
<button type="submit">Submit</button>
</form>
在這個 HTML 表單中:
name="Name" 是表單的字段名稱,對應到 Product 對象的 Name 屬性。
name="Price" 對應到 Product 對象的 Price 屬性。
3. 控制器中的方法:
csharp
複製程式碼
[HttpPost]
public IActionResult Create(Product product)
{
if (ModelState.IsValid)
{
// 處理產品邏輯,例如將產品保存到數據庫
return RedirectToAction("Details", new { id = product.Id });
}
return View(product);
}
當表單被提交後,ASP.NET Core 會自動將表單字段 Name 和 Price 的值繫結到 Product product 中的對應屬性 Name 和 Price。
如果 ModelState 是有效的,說明繫結過程成功且數據通過了驗證。
具體繫結過程
表單數據: 當用戶在前端表單中填寫以下數據並提交:
html
複製程式碼
Name: "Laptop"
Price: "999.99"
繫結到控制器中的 Product 對象: ASP.NET Core 會自動將這些表單字段中的值繫結到控制器的 Product 對象中的對應屬性:
csharp
複製程式碼
// 這裡的 "product" 將擁有表單數據
Product product = new Product
{
Name = "Laptop",
Price = 999.99M
};
表單中的 name="Name" 的值 "Laptop" 會被繫結到 product.Name。
表單中的 name="Price" 的值 "999.99" 會被繫結到 product.Price。
最終對象的值: 控制器方法中的 Product product 對象在繫結完成後將如下所示:
csharp
複製程式碼
product.Name = "Laptop"; // 來自表單字段 "Name"
product.Price = 999.99M; // 來自表單字段 "Price"
總結
表單字段中的數據 由 <input> 標籤的 name 屬性指定,例如 name="Name" 和 name="Price"。
模型屬性 由 Product 模型中的屬性指定,例如 Name 和 Price。
當用戶提交表單時,ASP.NET Core 的模型繫結功能會自動將表單字段中的數據與 Product 模型的屬性進行對應並繫結到控制器中的 Product 對象。
這種自動繫結的過程讓開發者能夠更簡單地從表單 POST 請求中取得複雜的對象,而不需要手動解析每個字段。
讓我詳細解釋一下 Razor View Engine 和 Global Action Filters,並展示如何在 ASP.NET MVC 框架中使用它們,對比過去和現在的寫法。
Razor View Engine
過去:使用 Web Forms View Engine (.aspx)
在早期的 ASP.NET MVC 中,視圖引擎是 Web Forms View Engine(即 .aspx),視圖文件中嵌入 C# 代碼需要使用 <%= %> 等語法,這使得語法相對繁瑣且難以閱讀。
舉例:早期使用 Web Forms 的視圖寫法
html
複製程式碼
<html>
<body>
<h1>Product Details</h1>
<div>
Product Name: <%= Model.Name %> <br />
Product Price: <%= Model.Price %> <br />
</div>
</body>
</html>
這裡使用 <%= %> 來嵌入 C# 代碼,語法繁瑣且容易混亂,尤其當視圖中有大量代碼時。
現在:使用 Razor View Engine (.cshtml)
ASP.NET MVC 3 引入了 Razor 視圖引擎,語法更加簡潔。Razor 使用 @ 符號來嵌入 C# 代碼,並且能夠更直觀地區分 HTML 和 C#。
舉例:現在使用 Razor 的視圖寫法
html
複製程式碼
@model Product
<html>
<body>
<h1>Product Details</h1>
<div>
Product Name: @Model.Name <br />
Product Price: @Model.Price <br />
</div>
</body>
</html>
使用 @ 符號直接嵌入 C# 代碼,語法簡單且更加整潔。
Razor 引擎能夠智能區分 HTML 和 C#,避免混亂。
Global Action Filters:允許在全域範圍內註冊過濾器 (如驗證、日誌記錄等)。
在 ASP.NET 和 ASP.NET Core 中,過濾器 (Filters) 是一種允許在控制器操作方法執行之前或之後插入邏輯的機制。它們用來實現 橫切關注點,例如:
驗證 (Authorization)
日誌記錄 (Logging)
錯誤處理 (Exception handling)
快取 (Caching)
動作執行時間的度量 (Performance measurement)
這些功能通常不屬於業務邏輯,但需要在多個控制器或行動方法中統一應用。過濾器可以幫助我們避免重複代碼,並將這些橫切關注點封裝起來。
過濾器類型
ASP.NET 和 ASP.NET Core 提供了多種過濾器,每一種都可以在請求管道的不同階段發揮作用:
授權過濾器 (Authorization Filters):
在控制器操作方法執行之前,用於驗證當前用戶是否有權限執行該操作。
例如:[Authorize]。
資源過濾器 (Resource Filters):
在授權過濾器之後、動作方法執行之前和之後運行,可以用來實現快取等功能。
動作過濾器 (Action Filters):
在動作方法執行之前或之後運行,可以用於修改動作參數或日誌記錄等。
例如:OnActionExecuting 和 OnActionExecuted。
例外過濾器 (Exception Filters):
當控制器操作方法中發生未處理的異常時,例外過濾器會捕獲並處理異常。
例如:用來記錄異常信息或返回自定義錯誤頁面。
結果過濾器 (Result Filters):
在動作方法執行完畢並產生結果後,結果過濾器會運行,可以修改或處理該結果。
具體示例
假設我們想要實現一個自定義的 日誌記錄過濾器,該過濾器會在每個控制器操作方法執行之前和之後記錄相關的請求信息。
1. 定義自定義過濾器
csharp
複製程式碼
public class LogActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// 在控制器操作方法執行之前記錄日誌
Console.WriteLine("Action Executing: " + context.ActionDescriptor.DisplayName);
}
public void OnActionExecuted(ActionExecutedContext context)
{
// 在控制器操作方法執行之後記錄日誌
Console.WriteLine("Action Executed: " + context.ActionDescriptor.DisplayName);
}
}
2. 在控制器中應用過濾器
可以將過濾器應用到某個控制器或行動方法上:
csharp
複製程式碼
[LogActionFilter]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
3. 全局註冊過濾器
也可以將過濾器註冊為全局過濾器,這樣它將應用到所有控制器和行動方法:
csharp
複製程式碼
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Filters.Add(new LogActionFilter()); // 註冊全局過濾器
});
}
過濾器的作用
簡化代碼:將重複的邏輯(如日誌記錄、驗證)抽取到過濾器中,可以減少控制器和行動方法中的重複代碼。
集中管理:過濾器可以應用於單個控制器、單個行動方法,甚至全局範圍內,讓橫切關注點更易於管理和修改。
可插拔性:過濾器的執行是可選擇和可插拔的,可以根據需求應用或移除過濾器,讓應用更靈活。
常見過濾器
[Authorize]:驗證過濾器,用於限制未經授權的用戶訪問某些操作。
[HandleError]:異常過濾器,用於處理應用程序中的未處理異常。
[ActionFilter]:自定義動作過濾器,用於在操作方法前後執行額外的邏輯。
總結
過濾器是一個強大的工具,可以在 ASP.NET 和 ASP.NET Core 應用中幫助實現通用的邏輯,如驗證、日誌記錄和異常處理等,讓代碼更加簡潔、可維護。
Dependency Injection 支持:允許簡單整合依賴注入容器 (如 Ninject、Autofac)。
JSON 支持改進:加強了與 JavaScript 和 JSON 的交互,包括改進了 JsonResult。
4. ASP.NET MVC 4 (2012)
Web API 支持:引入 ASP.NET Web API,專門處理 RESTful API 請求,擴展了 MVC 的功能。
Display Modes:根據裝置或瀏覽器來決定使用哪一種視圖,例如為桌面和移動設備設計不同的頁面。
Display Modes 是 ASP.NET MVC 提供的一個功能,允許根據用戶的設備(如桌面、平板、手機)自動選擇不同的視圖模板。這樣開發者可以為不同的設備設計不同的視圖,而不需要編寫額外的邏輯來判斷使用哪一個視圖。
這個功能在桌面和移動設備之間進行視圖切換時特別有用,例如提供更符合移動體驗的界面。
Display Modes 的工作原理
Display Modes 會根據請求中的 User-Agent 標頭來決定用戶的裝置類型,並選擇匹配的視圖文件。如果該裝置有對應的視圖,Display Modes 會自動載入適合的視圖。如果沒有匹配的視圖,則使用默認的視圖。
具體視圖選擇邏輯:
假設有一個 Index.cshtml 視圖:
如果訪問的裝置是桌面,使用 Views/Home/Index.cshtml。
如果訪問的裝置是手機,則使用 Views/Home/Index.Mobile.cshtml。
ASP.NET 會自動檢查是否存在針對手機的 Index.Mobile.cshtml 視圖,如果存在則使用它,否則退回到默認的 Index.cshtml 視圖。
步驟與示範
1. 建立控制器
首先,我們需要一個基本的控制器。假設我們有一個 HomeController:
csharp
複製程式碼
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
2. 建立桌面版視圖 (Views/Home/Index.cshtml)
這是針對桌面瀏覽器的標準視圖。
html
複製程式碼
@{
ViewBag.Title = "Desktop View";
}
<h2>Welcome to the Desktop version</h2>
<p>This is the view for desktop devices.</p>
3. 建立移動版視圖 (Views/Home/Index.Mobile.cshtml)
這是針對移動設備的視圖。
html
複製程式碼
@{
ViewBag.Title = "Mobile View";
}
<h2>Welcome to the Mobile version</h2>
<p>This is the view for mobile devices.</p>
4. 設定 Display Mode
在 ASP.NET MVC 中,Display Modes 可以自動識別手機瀏覽器,預設情況下 MVC 框架已經內建了針對某些設備的識別,但也可以自定義。
如果需要添加自定義的模式,可以在 Global.asax.cs 中使用 DisplayModeProvider.Instance.Modes 來擴展設備識別。例如,假設我們想要添加針對 平板設備 的視圖模式:
csharp
複製程式碼
protected void Application_Start()
{
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Tablet")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf("iPad", StringComparison.OrdinalIgnoreCase) >= 0)
});
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
這段代碼的意思是當 User-Agent 中包含 "iPad" 時,ASP.NET MVC 將會嘗試尋找視圖文件名為 Index.Tablet.cshtml 的文件。如果找到,則使用該文件來渲染視圖。
5. 建立平板版視圖 (Views/Home/Index.Tablet.cshtml)
html
複製程式碼
@{
ViewBag.Title = "Tablet View";
}
<h2>Welcome to the Tablet version</h2>
<p>This is the view optimized for tablet devices.</p>
顯示過程
如果用戶使用桌面瀏覽器訪問 /Home/Index,Display Modes 會自動選擇 Views/Home/Index.cshtml 進行渲染。
如果用戶使用手機瀏覽器訪問 /Home/Index,Display Modes 會自動選擇 Views/Home/Index.Mobile.cshtml。
如果用戶使用 iPad 訪問,並且我們已經設置了平板模式,則會使用 Views/Home/Index.Tablet.cshtml。
自定義 Display Mode
如果你想要根據特定條件自定義 Display Mode(比如根據某個特定的瀏覽器或屏幕大小),可以使用 ContextCondition 來判斷。
例如,針對 Chrome 瀏覽器自定義一個 Display Mode:
csharp
複製程式碼
protected void Application_Start()
{
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Chrome")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf("Chrome", StringComparison.OrdinalIgnoreCase) >= 0)
});
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
這樣,當使用 Chrome 瀏覽器訪問時,ASP.NET MVC 將會尋找 Index.Chrome.cshtml 視圖。
Task-based Asynchronous Methods:支持非同步控制器方法,允許處理異步任務以提升性能。
Bundling 和 Minification:自動捆綁和壓縮 CSS 和 JavaScript 文件,改善應用程式的性能。
5. ASP.NET MVC 5 (2013)
Attribute Routing:除了傳統的路由定義方式,還支持用屬性來定義路由,更加靈活。
Attribute Routing 是 ASP.NET Core 中的一種路由定義方式,與傳統的路由表配置方式不同,它允許開發者直接在控制器和動作方法上使用屬性來定義路由。這使得路由的配置更加靈活和直觀,特別是在應對複雜的路由規則時,屬性路由會更加便捷。
傳統路由 vs Attribute Routing
傳統的路由定義: 傳統路由是在 Startup.cs 文件中配置的,通常使用 MapControllerRoute 或類似的方法來定義路由。這種方式適合較簡單、全局性的路由規則。
csharp
複製程式碼
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Attribute Routing: Attribute Routing 是在控制器或動作方法上通過屬性標註來定義路由,讓每個控制器或方法有自己專屬的路由邏輯,這使得它非常適合設計 RESTful API。
使用示範
以下是一個使用 Attribute Routing 的例子:
csharp
複製程式碼
[Route("products")]
public class ProductsController : Controller
{
// 匹配 /products
[HttpGet]
public IActionResult GetAllProducts()
{
// 獲取所有產品
return Ok(/* 產品列表 */);
}
// 匹配 /products/5
[HttpGet("{id}")]
public IActionResult GetProductById(int id)
{
// 根據ID獲取產品
return Ok(/* 單個產品 */);
}
// 匹配 /products/category/electronics
[HttpGet("category/{categoryName}")]
public IActionResult GetProductsByCategory(string categoryName)
{
// 根據類別獲取產品
return Ok(/* 該類別下的產品 */);
}
// 匹配 /products
[HttpPost]
public IActionResult CreateProduct([FromBody] Product product)
{
// 創建新產品
return CreatedAtAction(nameof(GetProductById), new { id = product.Id }, product);
}
}
解釋
[Route("products")]:
這個屬性定義了 ProductsController 的路由前綴為 /products,即所有該控制器的動作方法的路由都以 products 開頭。
[HttpGet]:
標註 GetAllProducts 方法處理 GET 請求,並且路由為 /products,這是因為方法沒有額外的路由定義。
[HttpGet("{id}")]:
定義了一個包含路徑參數 id 的 GET 路由,即 /products/{id}。這會將 URL 中的 id 參數自動映射到方法的 id 參數上。
[HttpGet("category/{categoryName}")]:
定義了一個自定義路由 /products/category/{categoryName},這裡的 categoryName 是一個路徑參數,它會自動映射到方法的 categoryName 參數。
[HttpPost]:
定義了一個處理 POST 請求的路由 /products,用來創建新產品。這裡的 CreatedAtAction 會返回 201 Created 的狀態碼,並包含新創建產品的 URI。
優點
更靈活:每個控制器和方法都可以定義自己的路由邏輯,支持路徑參數、查詢參數等。
更直觀:路由邏輯直接寫在控制器上,便於查看和維護。
RESTful 支持:很適合構建 REST API,每個方法可以清楚地對應 HTTP 動詞和路徑。
ASP.NET Identity:引入了 ASP.NET Identity 進行認證和授權,替代了舊的 Membership 系統。
Bootstrap Integration:預設使用 Twitter Bootstrap,讓開發者更容易創建響應式 UI。
One ASP.NET:將 Web Forms、MVC、Web API 整合進同一個框架,開發者可以混合使用這些技術。
[一個 ASP.NET:整合 ASP.NET Web Form、MVC 與 Web API](https://learn.microsoft.com/zh-tw/aspnet/visual-studio/overview/2013/one-aspnet-integrating-aspnet-web-forms-mvc-and-web-api)
Blazor 示範:計算器
我們將示範如何使用 Blazor WebAssembly 來構建一個簡單的計算器應用。
1. 創建 Blazor WebAssembly 項目
在 Visual Studio 中:
選擇 "Blazor WebAssembly App" 模板來創建一個 Blazor WASM 項目。
2. 編寫計算器組件
在 Blazor 中,每個頁面或組件都是一個 .razor 文件。以下是一個簡單的計算器組件:
razor
複製程式碼
@page "/calculator"
<h3>簡單計算器</h3>
<div>
<input @bind="num1" placeholder="數字1" /> +
<input @bind="num2" placeholder="數字2" /> =
<input value="@sum" readonly />
<button @onclick="CalculateSum">計算</button>
</div>
@code {
private int num1;
private int num2;
private int sum;
private void CalculateSum()
{
sum = num1 + num2;
}
}
3. 解釋代碼
@page "/calculator":這一行表示這個組件對應 /calculator 路由,當用戶訪問 /calculator URL 時,這個組件將會顯示。
@bind:Blazor 使用 @bind 將 HTML 元素的值和 C# 變量綁定在一起。這裡我們將兩個輸入框分別綁定到 num1 和 num2。
@onclick:這個屬性將按鈕的點擊事件綁定到 C# 的 CalculateSum 方法,按下按鈕時會執行此方法來計算兩個數字的和。
@code { ... }:這部分是用來編寫 C# 代碼邏輯的區域。這裡我們定義了 num1, num2 和 sum 三個變量,並實現了 CalculateSum 方法,計算兩個數字的和。
4. 執行應用
運行應用後,瀏覽器將加載 WebAssembly 版本的 Blazor 應用。當用戶訪問 /calculator 頁面時,將會看到一個簡單的加法計算器。用戶輸入兩個數字,點擊「計算」按鈕,應用會使用 C# 代碼來計算兩數之和,並顯示結果。
Blazor 的優勢
全棧 C# 開發:開發者可以使用 C# 來處理前後端邏輯,減少了前後端技術棧的切換,提升了代碼重用性。
組件化:Blazor 中的組件可以重用和嵌套,讓代碼結構更加清晰且模塊化。
WebAssembly 支持:Blazor WebAssembly 使得 C# 可以直接在瀏覽器中運行,提供了一個原生級性能的用戶體驗。
減少 JavaScript 依賴:在一些應用場景中,可以完全用 C# 來替代 JavaScript,大大減少 JavaScript 的代碼量和錯誤風險。
非同步所有內容:強調以非同步方式處理請求,提升性能並減少資源佔用。
更靈活的路由系統:隨著 .NET Core 的演進,路由系統得到了進一步的改進。
高度模組化:框架更加模組化,開發者可以只引入需要的部分。
在 ASP.NET Core 中,這些改進(非同步處理、靈活的路由系統、高度模組化)是相對於舊版 ASP.NET Framework 的重大進化,這些變化提高了應用程序的性能、靈活性和可維護性。
1. 非同步所有內容(Async Everything)
非同步編程 可以提高應用的性能,特別是在高並發環境中,通過釋放 CPU 資源來處理更多的請求。ASP.NET Core 強調以非同步方式處理幾乎所有的請求和操作,這與舊版 ASP.NET Framework 中的大量同步操作形成了鮮明對比。
ASP.NET Framework (同步處理)
在舊版的 ASP.NET Framework 中,許多控制器操作是同步的,這導致當進行 I/O 操作時(例如讀取數據庫或調用外部 API),線程會被阻塞,直到操作完成。
csharp
複製程式碼
// ASP.NET Framework 同步方法
public ActionResult GetData()
{
var data = database.GetData(); // 同步調用,線程阻塞
return View(data);
}
ASP.NET Core (非同步處理)
在 ASP.NET Core 中,幾乎所有的操作建議都使用 async/await 非同步模式。非同步操作可以避免線程阻塞,這樣在進行 I/O 操作時,可以釋放線程來處理其他請求。
csharp
複製程式碼
// ASP.NET Core 非同步方法
public async Task<IActionResult> GetDataAsync()
{
var data = await database.GetDataAsync(); // 非同步調用,不阻塞線程
return View(data);
}
這種方式提高了並發性能,尤其在面對大量 I/O 操作時(例如數據庫查詢、文件操作或網絡調用)。
2. 更靈活的路由系統
ASP.NET Core 引入了更加靈活和強大的路由系統,允許開發者以更簡單的方式定義和管理路由。它擁有集中式路由和屬性路由,支持複雜的路徑匹配與條件。
ASP.NET Framework 路由
在 ASP.NET Framework 中,路由系統是集中定義的,通常在 Global.asax 中配置。
csharp
複製程式碼
// ASP.NET Framework 路由
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
這種路由系統雖然簡單,但缺乏靈活性和可擴展性,對於複雜的路由需求(如多個參數、多樣化 URL 模式等)處理起來較為笨拙。
ASP.NET Core 路由
ASP.NET Core 提供了更靈活的路由系統,允許使用 屬性路由 和 集中式路由。此外,還可以更靈活地管理路由參數、約束等。
屬性路由:允許直接在控制器或方法上定義路由規則。
csharp
複製程式碼
// ASP.NET Core 屬性路由
[Route("api/products")]
public class ProductsController : Controller
{
[HttpGet("{id:int}")] // 限制 id 必須是整數
public IActionResult GetProduct(int id)
{
// 獲取產品邏輯
return Ok(product);
}
}
集中式路由:可以使用 MapControllerRoute 或 MapDefaultControllerRoute 來定義集中路由規則。相比於 ASP.NET Framework,ASP.NET Core 的集中路由更具靈活性,支持多個路由規則和條件。
csharp
複製程式碼
// ASP.NET Core 集中式路由
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
此外,ASP.NET Core 還支持基於路由模板的條件化路由,如根據請求的 HTTP 方法或參數來匹配不同的路由。
3. 高度模組化
ASP.NET Core 是一個高度模組化的框架,允許開發者只引入需要的組件。相比較,舊版的 ASP.NET Framework 包含了一個龐大的基礎框架,開發者無法輕易裁剪不需要的功能。
ASP.NET Framework(不模組化)
在舊版 ASP.NET Framework 中,應用程序預設會引用整個系統庫和功能,即使某些部分並不會被使用到。這種情況下,應用程序的體積較大,並且載入和執行時的性能也受到了影響。
csharp
複製程式碼
// ASP.NET Framework 自動加載大量組件
// 無法靈活選擇需要的組件
ASP.NET Core(模組化)
在 ASP.NET Core 中,所有功能都是模組化的,開發者可以使用 NuGet 包管理器來引入所需的功能庫。這意味著應用可以更輕量,性能更佳,並且開發者有更多的控制權來定制應用。
csharp
複製程式碼
// ASP.NET Core 引入所需的模組
// 只引入需要的功能
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="3.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.10" />
ASP.NET Core 還允許開發者自定義中間件管道,只加載和配置應用程序需要的中間件,如身份驗證、路由、錯誤處理等。這提高了靈活性和性能。
csharp
複製程式碼
// ASP.NET Core 中定制中間件管道
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
總結前後差別
非同步處理:ASP.NET Core 對非同步編程進行了更深入的集成,幾乎所有的 I/O 操作都可以使用 async/await 來非同步處理,提高了並發能力,而 ASP.NET Framework 則更多依賴同步操作。
靈活的路由系統:ASP.NET Core 的路由系統更加靈活和強大,支持屬性路由和集中式路由,並且可以更精確地控制路由匹配條件。相比較,ASP.NET Framework 的路由系統較為僵硬,靈活性有限。
高度模組化:ASP.NET Core 是高度模組化的框架,允許開發者只引入需要的功能,提高了性能和靈活性。而 ASP.NET Framework 是一個整體框架,無法輕易剝離不需要的部分,這導致了應用程序更重,性能較差。